%include    "pm.inc"    ; 常量, 宏, 以及一些说明

org 07c00h ; 引导代码
    jmp LABEL_BEGIN

; 定义含3个描述符（空、32位代码段、显存段）的GDT
[SECTION .gdt]
; GDT（定义全局描述符表。注意，为防止误操作，首个GDT表项必须为空）
;                                  段基址,  段界限,  属性
LABEL_GDT:        Descriptor 0,       0,         0      ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致32位代码段
LABEL_DESC_VIDEO:  Descriptor 0B8000h, 0ffffh,  DA_DRW  ; 彩色文本屏幕显存的首地址
; GDT 结束
; 其中，代码段的基址在后面计算后再赋值
; 代码段的长度=常量SegCode32Len，在程序末尾处计算

; 定义48位GDTR的参数结构GdtPtr（16位界限 + 32位基地址）
GdtLen  equ $ - LABEL_GDT   ; GDT长度
GdtPtr  dw  GdtLen - 1      ; GDT界限
        dd  0               ; GDT基地址（以后再赋值）

; GDT 选择符（=代码段和显存段在GDT中的偏移量）
SelectorCode32  equ LABEL_DESC_CODE32 - LABEL_GDT ; = 8
SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT ; = 16
; END of [SECTION .gdt]

; 16位代码段：计算32位代码段基址和GDT基地址、加载GDTR、切换到保护模式
[SECTION .s16]
[BITS   16]
LABEL_BEGIN:
    mov ax, cs  ; 设置DS/ES/SS = CS（=0）
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 7c00h   ; 设置 SP = 100h

    ; 初始化 32 位代码段描述符中的基址部分
    xor eax, eax    ; EAX = 0
    mov ax, cs
    shl eax, 4  ; EAX = 代码段基址
    add eax, LABEL_SEG_CODE32   ; EAX + 偏移地址 = 32位代码段起始地址
    mov word [LABEL_DESC_CODE32 + 2], ax    ; B0~15 = AX
    shr eax, 16 ; EAX >> 16（AX = EAX的高16位）
    mov byte [LABEL_DESC_CODE32 + 4], al        ; B16~23 = AL
    mov byte [LABEL_DESC_CODE32 + 7], ah    ; B24~31 = AH

    ; 为加载 GDTR 作准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT      ; eax = GDT基地址 = DS<<4 + LABEL_GDT
    mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <-- GDT基地址

    ; 加载 GDTR
    lgdt    [GdtPtr]

    ; 关中断
    cli

    ; 打开地址线A20
    in  al, 92h
    or  al, 00000010b ; or al, 2（关闭：and al,0FDh）
    out 92h, al

    ; 准备切换到保护模式（置CR0的PE位为1）
    mov eax, cr0
    or  eax, 1  ; PE = 1
    mov cr0, eax

    ; 真正进入保护模式
    jmp dword SelectorCode32:0  ; 执行这一句会把SelectorCode32 装入cs,
                            ; 并跳转到Code32Selector:0处
; END of [SECTION .s16]

; 32 位代码段：通过修改显存，在彩色文本屏幕的第12行右端，显示红色字母P
[SECTION .s32] ; 32 位代码段. 由实模式跳入.
[BITS   32]

LABEL_SEG_CODE32:
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择符(目的)

    mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
    mov ah, 0Ch         ; 0000: 黑底    1100: 亮红字
    mov al, 'P'
    mov [gs:edi], ax

    ; 到此停止
    jmp $   ; 死循环

SegCode32Len    equ $ - LABEL_SEG_CODE32 ; 计算32位代码段的段长
; END of [SECTION .s32]
